from pyo import *

class _OscDevice:
    def __init__(self, name, dataInPort, audioPort, audioStreams, ip, dataOutPort = None, interpolation = True):
        """
        _OscDevice(name, dataPort, audioPort,audioStreams, ip):
            Class thats takes cares of communication with a specific destination
            Should not be instantiated by user, 
            see module function addCtrlr to instantiate
            
            Parameters:
            
            name: string, used to store and identify
            dataPort: int, udp port to send and receive data on, tipically 8000
            audioPort: int, udp port to send and receive audio on, tipically 7999
            audioStreams: int, lenght of lists of audio controls received
            ip: ip address of controller
            
            note: each udp port number can only be used once
        """
        if dataOutPort == None:
            dataOutPort = dataInPort
        self.name = name
        self.settings = {"dataInPort":dataInPort, "audioPort":audioPort, "audioStreams": audioStreams, "ip":ip,"dataOutPort":dataOutPort}
        self.dataInRef = {} # used to get function to call for data in
        self.oscDataIn = OscDataReceive(self.settings["dataInPort"],"/dump", self._parse)
        self.oscAudioIn = OscListReceive(self.settings["audioPort"], "/dump", self.settings["audioStreams"])
        # if interpolation == False:
        #             self.oscAudioIn.setInterpolation(0)
        self.oscDataOut = OscDataSend("s", self.settings["dataOutPort"], "/dump", self.settings["ip"])
    
    def setAudio(self, mydict):
        for adr, value in mydict.iteritems():
            self.oscAudioIn.setValue(adr, value)
            
    
    def addAudioIn(self, address):
        """
        adds an address to the OscListReceive
        note: there must be the number of floats in the osc message specified in the audioStream variable
        """
        if not address in self.oscAudioIn.getAddresses():
            self.oscAudioIn.addAddress(address)
        else:
            pass
    
    def getAudio(self, address, streams):
        if address in self.oscAudioIn.getAddresses():
            if len(streams) == 1:# put in type??
                return Sig(self.oscAudioIn[address][streams[0]])
            else:
                return Sig([self.oscAudioIn[address][i] for i in streams])
        else:
            print("Warning! oscAudio address not stored!")
            return Sig(0)
        
    def addDataOut(self, address, type):
        """
        addDataOut(address, type)
            adds a data control's address to OscDataSend 
        """
        self.oscDataOut.addAddress(type, self.settings["dataOutPort"], address, self.settings["ip"])
    
    def addDataIn(self, ctrlDict):
        """
        addDataIn(ctrlDict)
            adds a data control's address to OscDataReceive 
            and stores control's dictionary to get function to call
        """
        self.dataInRef[ctrlDict["adrIn"]] = ctrlDict
        self.oscDataIn.addAddress(ctrlDict["adrIn"])
    
    def _parse(self, address, *args):
        if len(args) == 1:
            value = args[0]
        elif len(args) > 1:
            value = list(args)
        try:
            self.dataInRef[address]["value"] = value 
            if self.dataInRef[address]["func"]:
                self.dataInRef[address]["func"](self.dataInRef[address]["value"])
        except KeyError:
            print "warning Osc Data Address %s not stored! could not find function to forward to" % address
    
    def sendData(self, msg, address):
        """
        sends data message
        """
        self.oscDataOut.send(msg, address = address)
    
    def deleteAddresses(self, adrIn, adrOut, ctrlType):
        if ctrlType == "audio":
            self.oscDataOut.delAddress(adrOut)
            if adrIn in self.oscAudioIn.getAddresses():
                self.oscAudioIn.delAddress(adrIn)
        elif ctrlType == "data":
            self.oscDataOut.delAddress(adrOut)
            self.oscDataIn.delAddress(adrIn)
            del self.dataInRef[adrIn]
        elif ctrlType == "dataOut":
            self.oscDataOut.delAddress(adrOut)
    #create function for OscAudioOut

_oscDict = {}

def addCtrlr(name, dataInPort, audioPort, audioStreams, ip, dataOutPort = None):
    """
    addCtrlr(name, dataPort, audioPort,audioStreams, ip):
        called from script to initialize an OSC controller
        
        Parameters:
        
        name: string, used to store and identify
        dataInPort: int, udp port to send and receive data on, tipically 8000
        audioPort: int, udp port to send and receive audio on, tipically 7999
        audioStreams: int, lenght of lists of audio controls received
        ip: ip address of controller
        dataOutPort: address of output port. must be different from input port when sending internally. if None same as In
        
        note: each udp port number can only be used once
    """
    _oscDict[name] = _OscDevice(name, dataInPort, audioPort, audioStreams, ip, dataOutPort)


class OscCtrls:
    """
    OscCtrls: group of OSC controls that simplifies comunication with OscServer
    Built to simplify use in class: 
        use addressPrepend argument to prepend message to all osc addresses
        in class, usefull with containers in lemur
        call preDelete() in class __del__ to clean up osc addresses and various signals
    functions:
    
        newAudio(name, streams, adrIn, adrOut, ctrlr)
            used to initialize an audio control. returns audio streams
        
        newData(name, type, func, adrIn, adrOut, ctrlr)
            used to initialize a data control. func gets called when message arives
        
        send(name, value)
            used to send a value
            
        preDelete()
            takes care of deleting all the data that was used by this class
    """
    def __init__(self, addressPrepend = None): 
        self.oscDict = _oscDict #reference to OscDevices where comunication happens (used for ctrl's init and send calls)
        self.addressPrepend = addressPrepend
        self.ctrls = {} 
        self.silence = Sig(0)
        self.audioInit = {}#NEW
    
    def newAudio(self, name, streams, adrIn, adrOut, ctrlr, initVal = None):
        """
        newAudio(name, streams, adrIn, adrOut, ctrlr)
            used to initialize an audio control. returns audio streams
            
            Parameters:
            
            name: string used to store and identify
            streams: list of ints, streams to return
            adrIn: string, address that receives audio streams
            adrOut: string, address to use with function send(name, value)
            ctrlr: string, name of controller to send receive to
            initVal
        """
        if name not in self.ctrls.keys():
            if self.addressPrepend:
                adrIn = self.addressPrepend+adrIn
                adrOut = self.addressPrepend+adrOut
            self.oscDict[ctrlr].addAudioIn(adrIn)
            self.ctrls[name] = {
            "type": "f"*len(streams),
            "ctrltype": "audio",
            "adrIn": adrIn, 
            "streams": streams,
            "adrOut": adrOut,
            "ctrlr": ctrlr, #lemur,miniLemur, wii (p5, quneo)
            "init": initVal,#NEW
            "audio":self.oscDict[ctrlr].getAudio(adrIn, streams)
            }
            if ctrlr not in self.audioInit:
                self.audioInit[ctrlr] = {}
            if adrIn not in self.audioInit[ctrlr]: #NEW
                self.audioInit[ctrlr][adrIn] = [0]*self.oscDict[ctrlr].settings["audioStreams"] #NEW
            if initVal: #NEW
                for i in range(len(streams)):
                    self.audioInit[ctrlr][adrIn][streams[i]] = initVal[i]#NEW
            
            self.oscDict[ctrlr].addDataOut(adrOut, self.ctrls[name]["type"])
        return self.ctrls[name]["audio"]
    
    def newData(self, name, type, func, adrIn, adrOut, ctrlr):
        """
        newData(name, type, func, adrIn, adrOut, ctrlr)
            used to initialize a data control. func gets called when message arives
            
            Parameters:
            
            name: string used to store and identify
            type: String specifying the types of the data
                Possible values are:
                    integer: "i"
                    long integer: "h"
                    float: "f"
                    double: "d"
                    string: "s"
            func: function called when data arrives with data as argument
            adrIn: string, address that receives data and calls function
            adrOut: string, address to use with function send(name, value) if None same as adrIn
            ctrlr: string, name of controller to send receive to 
        """
        if name not in self.ctrls.keys():
            if adrOut == None:
                adrOut = adrIn
            if self.addressPrepend:
                adrIn = self.addressPrepend+adrIn
                adrOut = self.addressPrepend+adrOut
            self.ctrls[name] = {
            "value": None,
            "type": type,
            "ctrltype": "data",
            "adrIn": adrIn,
            "adrOut": adrOut,
            "ctrlr": ctrlr,
            "func": func
            }
            self.oscDict[ctrlr].addDataIn(self.ctrls[name])
            self.oscDict[ctrlr].addDataOut(adrOut, type)
    
    def newDataOut(self, name, type, adrOut, ctrlr):
        """
        newDataOut(name, type, adrOut, ctrlr)
            used to initialize a simple data ouput.
            
            Parameters:
            
            name: string used to store and identify
            type: String specifying the types of the data
                Possible values are:
                    integer: "i"
                    long integer: "h"
                    float: "f"
                    double: "d"
                    string: "s"
            adrOut: string, address to use with function send(name, value)
            ctrlr: string, name of controller to send to 
        """
        if name not in self.ctrls.keys():
            if self.addressPrepend:
                adrOut = self.addressPrepend+adrOut
            self.ctrls[name] = {
            "value": None,
            "type": type,
            "ctrltype": "dataOut",
            "adrIn": None,
            "adrOut": adrOut,
            "ctrlr": ctrlr
            }
            self.oscDict[ctrlr].addDataOut(adrOut, type)
    
    def send(self, name, value):
        """
        send(name, value)
            used to send messages to OSC targets
            
            Parameters:
            
            name: string of stored control, used to get address to send to
            value: value of message sent. 
                Note: 
                    - In case of data control must be the same type as declared
                    - In case of audio control must be the same lenght as the numbers of streams declared
        """
        ctrltype = self.ctrls[name]["type"]
        if not value == None:
            msg = value
        else:
            if ctrltype == "audio":
                msg = self.ctrls[name]["audio"].get(true)
            elif ctrltype in ["data", "UI"]:
                msg = self.ctrls[name]["value"]
        
        address = self.ctrls[name]["adrOut"]
        if type(msg) != list:
            msg = [msg]
        self.oscDict[self.ctrls[name]["ctrlr"]].sendData(msg, address)
    
    def preDelete(self):
        """
        preDelete()
            takes care of deleting all the data that was used by this class: addresses added to _oscDevice
            Silences Osc audio in and sends addresses and control type to OscDevice for deletion
        """
        print "predeleting!"
        for name, ctrl in self.ctrls.items():
            if ctrl["ctrltype"] == "audio":
                ctrl["audio"].setValue(self.silence)
            self.oscDict[ctrl["ctrlr"]].deleteAddresses(ctrl["adrIn"], ctrl["adrOut"], ctrl["ctrltype"])
        del self.ctrls
        del self.silence
        
    def initAudio(self):
        """
        initAudio()
            initialises osc audio controls
        """
        for ctrlr, values in self.audioInit.iteritems():
            print ctrlr, values
            self.oscDict[ctrlr].setAudio(values)




